package com.p6spy.engine.logging;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.management.StandardMBean;

import com.p6spy.engine.common.P6Util;
import com.p6spy.engine.spy.P6ModuleManager;
import com.p6spy.engine.spy.option.P6OptionsRepository;

public class P6LogOptions extends StandardMBean implements P6LogLoadableOptions {

    public static final String EXCLUDE = "exclude";
    public static final String INCLUDE = "include";
    public static final String FILTER = "filter";
    public static final String EXCLUDECATEGORIES = "excludecategories";
    public static final String EXCLUDEBINARY = "excludebinary";
    public static final String EXECUTION_THRESHOLD = "executionThreshold";
    public static final String SQLEXPRESSION = "sqlexpression";

    // those set indirectly (via properties visible from outside)
    public static final String INCLUDE_LIST = "includeList";
    public static final String EXCLUDE_LIST = "excludeList";
    public static final String INCLUDE_EXCLUDE_PATTERN = "includeExcludePattern";
    public static final String EXCLUDECATEGORIES_SET = "excludecategoriesSet";
    public static final String SQLEXPRESSION_PATTERN = "sqlexpressionPattern";

    protected static final Map<String, String> defaults;

    static {
        defaults = new HashMap<String, String>();

        defaults.put(FILTER, Boolean.toString(false));
        defaults.put(EXCLUDECATEGORIES, "info,debug,result,resultset,batch");
        defaults.put(EXCLUDEBINARY, Boolean.toString(false));
        defaults.put(EXECUTION_THRESHOLD, Long.toString(0));
    }

    private final P6OptionsRepository optionsRepository;

    public P6LogOptions(final P6OptionsRepository optionsRepository) {
        super(P6LogOptionsMBean.class, false);
        this.optionsRepository = optionsRepository;
    }

    @Override
    public void load(Map<String, String> options) {

        setSQLExpression(options.get(SQLEXPRESSION));
        setExecutionThreshold(options.get(EXECUTION_THRESHOLD));

        setExcludecategories(options.get(EXCLUDECATEGORIES));

        setFilter(options.get(FILTER));
        setInclude(options.get(INCLUDE));
        setExclude(options.get(EXCLUDE));
        setExcludebinary(options.get(EXCLUDEBINARY));
    }

    /**
     * Utility method, to make accessing options from app less verbose.
     *
     * @return active instance of the {@link P6LogLoadableOptions}
     */
    public static P6LogLoadableOptions getActiveInstance() {
        return P6ModuleManager.getInstance().getOptions(P6LogOptions.class);
    }

    @Override
    public Map<String, String> getDefaults() {
        return defaults;
    }

    // JMX exposed API

    @Override
    public void setExclude(String exclude) {
        optionsRepository.setSet(String.class, EXCLUDE_LIST, exclude);
        // setting effective string
        optionsRepository.set(String.class, EXCLUDE, P6Util.joinNullSafe(optionsRepository.getSet(String.class, EXCLUDE_LIST), ","));
        optionsRepository.setOrUnSet(Pattern.class, INCLUDE_EXCLUDE_PATTERN, computeIncludeExcludePattern(), defaults.get(INCLUDE_EXCLUDE_PATTERN));
    }

    private String computeIncludeExcludePattern() {
        final String excludes = P6Util.joinNullSafe(optionsRepository.getSet(String.class, EXCLUDE_LIST), "|");
        final String includes = P6Util.joinNullSafe(optionsRepository.getSet(String.class, INCLUDE_LIST), "|");

        if (excludes.length() == 0 && includes.length() == 0) {
            return null;
        }

        final StringBuilder sb = new StringBuilder("(?mis)");

        if (excludes.length() > 0) {
            sb.append("(?!.*(").append(excludes).append(").*)");
        }

        if (includes.length() > 0) {
            sb.append("(.*(").append(includes).append(").*)");
        } else {
            // make sure to match any string here, if no explicit includes specified
            sb.append("(.*)");
        }

        return sb.toString();
    }

    @Override
    public String getExclude() {
        return optionsRepository.get(String.class, EXCLUDE);
    }

    @Override
    public void setExcludebinary(boolean excludebinary) {
        optionsRepository.set(Boolean.class, EXCLUDEBINARY, excludebinary);
    }

    @Override
    public void setExcludebinary(String excludebinary) {
        optionsRepository.set(Boolean.class, EXCLUDEBINARY, excludebinary);
    }

    @Override
    public boolean getExcludebinary() {
        return optionsRepository.get(Boolean.class, EXCLUDEBINARY);
    }

    @Override
    public void setExcludecategories(String excludecategories) {
        optionsRepository.set(String.class, EXCLUDECATEGORIES, excludecategories);
        optionsRepository.setSet(Category.class, EXCLUDECATEGORIES_SET, excludecategories);
    }

    @Override
    public String getExcludecategories() {
        return optionsRepository.get(String.class, EXCLUDECATEGORIES);
    }

    @Override
    public void setFilter(String filter) {
        optionsRepository.set(Boolean.class, FILTER, filter);
    }

    @Override
    public void setFilter(boolean filter) {
        optionsRepository.set(Boolean.class, FILTER, filter);
    }

    @Override
    public boolean getFilter() {
        return optionsRepository.get(Boolean.class, FILTER);
    }

    @Override
    public void setInclude(String include) {
        optionsRepository.setSet(String.class, INCLUDE_LIST, include);
        // setting effective string
        optionsRepository.set(String.class, INCLUDE, P6Util.joinNullSafe(optionsRepository.getSet(String.class, INCLUDE_LIST), ","));
        optionsRepository.setOrUnSet(Pattern.class, INCLUDE_EXCLUDE_PATTERN, computeIncludeExcludePattern(), defaults.get(INCLUDE_EXCLUDE_PATTERN));
    }

    @Override
    public String getInclude() {
        return optionsRepository.get(String.class, INCLUDE);
    }

    @Override
    public String getSQLExpression() {
        return optionsRepository.get(String.class, SQLEXPRESSION);
    }

    @Override
    public Pattern getSQLExpressionPattern() {
        return optionsRepository.get(Pattern.class, SQLEXPRESSION_PATTERN);
    }

    @Override
    public void setSQLExpression(String sqlexpression) {
        optionsRepository.set(String.class, SQLEXPRESSION, sqlexpression);
        optionsRepository.set(Pattern.class, SQLEXPRESSION_PATTERN, sqlexpression);
    }

    @Override
    public void unSetSQLExpression() {
        optionsRepository.setOrUnSet(String.class, SQLEXPRESSION, null, defaults.get(SQLEXPRESSION));
        optionsRepository.setOrUnSet(Pattern.class, SQLEXPRESSION_PATTERN, null, defaults.get(SQLEXPRESSION_PATTERN));
    }

    @Override
    public void setExecutionThreshold(String executionThreshold) {
        optionsRepository.set(Long.class, EXECUTION_THRESHOLD, executionThreshold);
    }

    @Override
    public void setExecutionThreshold(long executionThreshold) {
        optionsRepository.set(Long.class, EXECUTION_THRESHOLD, executionThreshold);
    }

    @Override
    public long getExecutionThreshold() {
        return optionsRepository.get(Long.class, EXECUTION_THRESHOLD);
    }

    @Override
    public Set<String> getIncludeList() {
        return optionsRepository.getSet(String.class, INCLUDE_LIST);
    }

    @Override
    public Set<String> getExcludeList() {
        return optionsRepository.getSet(String.class, EXCLUDE_LIST);
    }

    @Override
    public Pattern getIncludeExcludePattern() {
        return optionsRepository.get(Pattern.class, INCLUDE_EXCLUDE_PATTERN);
    }

    @Override
    public Set<Category> getExcludeCategoriesSet() {
        return optionsRepository.getSet(Category.class, EXCLUDECATEGORIES_SET);
    }
}
